#include <cstring>
#include "sdram.hpp"

/* 内存池(32字节对齐) */
static uint8_t *memBase = (uint8_t *)0XC01F4000;    /* 外部SDRAM内存池,前面2M给LTDC用了(1280*800*2) */

/* 内存管理表 */
static MT_TYPE *mapBase = (MT_TYPE *)0XC1E30000;       /* 外部SRAM内存池MAP */

static uint32_t my_mem_malloc(uint32_t size);  /* 内存分配(内部调用) */
static uint8_t my_mem_free(uint32_t offset);   /* 内存释放(内部调用) */

void sdram_init()
{
    extern SDRAM_HandleTypeDef hsdram1;

    FMC_SDRAM_CommandTypeDef Command;
    Command.AutoRefreshNumber = 1;
    Command.CommandMode = FMC_SDRAM_CMD_CLK_ENABLE;
    Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
    Command.ModeRegisterDefinition = 0;

    // 打开时钟
    HAL_SDRAM_SendCommand(&hsdram1, &Command, 0xFFFF);

    // 延时 1ms(至少200us)
    HAL_Delay(1);

    // 预充电
    Command.CommandMode = FMC_SDRAM_CMD_PALL;
    HAL_SDRAM_SendCommand(&hsdram1, &Command, 0xFFFF);

    // 8个自刷新周期
    Command.AutoRefreshNumber = 8;
    Command.CommandMode = FMC_SDRAM_CMD_AUTOREFRESH_MODE;
    HAL_SDRAM_SendCommand(&hsdram1, &Command, 0xFFFF);

    // 配置寄存器
    // [3:0] 突发长度: 1 2 4 8, 1111: 突发长度为全页
    // [6:4] CAS Latency (010: 2) (011: 3)
    // [9] 单次写 (0: 突发读与突发写) (1: 仅突发读)
    Command.ModeRegisterDefinition = 0b1'00'011'0000;
    Command.CommandMode = FMC_SDRAM_CMD_LOAD_MODE;
    Command.AutoRefreshNumber = 1;
    HAL_SDRAM_SendCommand(&hsdram1, &Command, 0xFFFF);

    // 刷新频率计数器
    // SDRAM 刷新周期为 64ms , SDCLK = 216/2 = 108Mhz, 行数为 8192(2^13).
    // 所以, cnt = 64/8192 * 108'000 - 20 = 823
    HAL_SDRAM_ProgramRefreshRate(&hsdram1, 823);

    memset(mapBase, 0, MEM_ALLOC_TABLE_SIZE * 4);  /* 内存状态表数据清零 */
}

/* 以下为正点原子内存管理的代码 */
/**
 * @brief       获取内存使用率
 * @retval      使用率(扩大了10倍,0~1000,代表0.0%~100.0%)
 */
uint16_t memUsed()  
{  
    uint32_t used = 0;  

    for (uint32_t i = 0; i < MEM_ALLOC_TABLE_SIZE; i++)  
    {  
        if (mapBase[i]) used++; 
    }

    return (used * 1000) / (MEM_ALLOC_TABLE_SIZE);  
}

/**
 * @brief       内存分配(内部调用)
 * @param       size : 要分配的内存大小(字节)
 * @retval      内存偏移地址
 *   @arg       0 ~ 0xFFFFFFFE : 有效的内存偏移地址
 *   @arg       0xFFFFFFFF     : 无效的内存偏移地址
 */
static uint32_t my_mem_malloc(uint32_t size)  
{  
    signed long offset = 0;  
    uint32_t nmemb;                                             /* 需要的内存块数 */
    uint32_t cmemb = 0;                                         /* 连续空内存块数 */
    uint32_t i;

    if (size == 0) return 0XFFFFFFFF;                           /* 不需要分配 */
    nmemb = size / MEM_BLOCK_SIZE;                            /* 获取需要分配的连续内存块数 */

    if (size % MEM_BLOCK_SIZE)
    {
        nmemb++;
    }

    for (offset = MEM_ALLOC_TABLE_SIZE - 1;offset >= 0; offset--)   /* 搜索整个内存控制区 */
    {
        cmemb = mapBase[offset] ? 0 : cmemb + 1;                  /* 连续空内存块数增加 */

        if (cmemb == nmemb)                                     /* 找到了连续nmemb个空内存块 */
        {
            for (i = 0; i < nmemb; i++)                          /* 标注内存块非空  */
            {  
                mapBase[offset + i] = nmemb;  
            }

            return (offset * MEM_BLOCK_SIZE);                 /* 返回偏移地址  */
        }
    }

    return 0XFFFFFFFF;                                          /* 未找到符合分配条件的内存块 */
}

/**
 * @brief       释放内存(内部调用)
 * @param       offset : 内存地址偏移
 * @retval      释放结果
 *   @arg       0, 释放成功;
 *   @arg       1, 释放失败;
 *   @arg       2, 超区域了(失败);
 */
static uint8_t my_mem_free(uint32_t offset)
{
    int i;

    if (offset < MEM_MAX_SIZE)                     /* 偏移在内存池内. */
    {
        int index = offset / MEM_BLOCK_SIZE;      /* 偏移所在内存块号码 */
        int nmemb = mapBase[index]; /* 内存块数量 */

        for (i = 0; i < nmemb; i++)                 /* 内存块清零 */
        {
            mapBase[index + i] = 0;
        }

        return 0;
    }
    else
    {
        return 2;                                  /* 偏移超区了. */
    }
}

/**
 * @brief       释放内存(外部调用)
 * @param       ptr  : 内存首地址
 * @retval      无
 */
void memFree(void *ptr)
{
    uint32_t offset;

    if (ptr == NULL) return;     /* 地址为0. */

    offset = (uint32_t)ptr - (uint32_t)memBase;
    my_mem_free(offset);  /* 释放内存 */
}

/**
 * @brief       分配内存(外部调用)
 * @param       size : 要分配的内存大小(字节)
 * @retval      分配到的内存首地址.
 */
void *memMalloc(uint32_t size)
{
    uint32_t offset = my_mem_malloc(size);

    if (offset == 0xFFFFFFFF)   /* 申请出错 */
    {
        return NULL;            /* 返回空(0) */
    }
    else                        /* 申请没问题, 返回首地址 */
    {
        return (void *)((uint32_t)memBase + offset);
    }
}

/**
 * @brief       重新分配内存(外部调用)
 * @param       *ptr : 旧内存首地址
 * @param       size : 要分配的内存大小(字节)
 * @retval      新分配到的内存首地址.
 */
void *memRealloc(void *ptr, uint32_t size)
{
    uint32_t offset = my_mem_malloc(size);

    if (offset == 0xFFFFFFFF)                                                          /* 申请出错 */
    {
        return NULL;                                                                   /* 返回空(0) */
    }
    else                                                                               /* 申请没问题, 返回首地址 */
    {
        memcpy((void *)((uint32_t)memBase + offset), ptr, size); /* 拷贝旧内存内容到新内存 */
        memFree(ptr);                                                             /* 释放旧内存 */
        return (void *)((uint32_t)memBase + offset);                  /* 返回新内存首地址 */
    }
}

